/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package com.flexengine.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

/**
 * @author Luann R. Athayde
 * @version 1.0
 * @since 1.0
 */
public class PersisterReader {
    
    public <T> T read(Class<? extends T> clazz, String file) throws Exception {
        return read(clazz, new File(file));
    }
    public <T> T read(Class<? extends T> clazz, File file) throws Exception {
        return read(clazz, new FileInputStream(file));
    }
    public <T> T read(Class<? extends T> clazz, InputStream in) throws Exception {
        if( clazz.getAnnotation(Root.class) == null ) {
            throw new NotRootTypedException("The element class["+clazz+"] is not a ROOT type!");
        }
        T obj = clazz.newInstance();
        Root root = (Root)clazz.getAnnotation(Root.class);
        
        XMLInputFactory inFactory = XMLInputFactory.newFactory();
        XMLStreamReader reader = inFactory.createXMLStreamReader(in);

        reader.next();
        String element = reader.getLocalName();
        if( !element.equals( !root.name().isEmpty() ? root.name() : clazz.getSimpleName() ) ) {
            throw new Exception("The XML file is not a ["+clazz+"] OBJECT!");
        }
        readAttributes(reader, clazz, obj);
        
        try {
            //int elements = 0;
            while( reader.hasNext() ) {
                if( reader.next() == XMLStreamReader.START_ELEMENT ) {
                    //elements++;
                    element = reader.getLocalName();
                    Field field = clazz.getDeclaredField(element);
                    if( field.getAnnotation(Element.class)!=null ) {
                        field.setAccessible(true);
                        readElement(reader, field, obj);
                    } else {
                        throw new ElementNotFoundException("The element ["+element+"] for class ["+clazz+"] is not defined!");
                    }
                }
            }
            //System.out.println("elements = " + elements);
        } catch(NoSuchFieldException nsfe) {
            throw new ElementNotFoundException("The element ["+element+"] for class ["+clazz+"] is not defined!");
        }
        
        return obj;
    }
    
    private void readAttributes(XMLStreamReader reader, Class clazz, Object obj) throws Exception {
        String attName = null, attValue;
        try {
            for(int i=0;i<reader.getAttributeCount();i++) {
                attName = reader.getAttributeLocalName(i);
                attValue = reader.getAttributeValue(i);
                Field f = null;
                System.out.println("class = "+clazz);
                System.out.println("field = "+f);
                Class tmpClass = clazz;
                System.out.println("tmpClass = "+tmpClass);
                while ( tmpClass != Object.class ) {
                    f = tmpClass.getDeclaredField(attName);
                    System.out.println("field = "+f);
                    if( f != null ) break;
                    tmpClass = clazz.getSuperclass();
                    System.out.println("tmpClass = "+tmpClass);
                }
                
                System.out.println("field = "+f);
                if( f!=null && f.getAnnotation(Attribute.class)!=null ) {
                    f.setAccessible(true);
                    f.set(obj, castItemValue(f.getType(), attValue) );
                } else {
                    throw new AttributeNotFoundException("The attribute ["+attName+"] for class ["+clazz+"] is not defined!");
                }
            }
        }
        catch(NoSuchFieldException nsfe) {
            throw new AttributeNotFoundException("The attribute ["+attName+"] for class ["+clazz+"] is not defined!");
        }
    }
    
    private void readElement(XMLStreamReader reader, Field field, Object obj) throws Exception {
        try {
            //System.out.println("readElement() - obj = "+obj);
            if( reader.nextTag() == XMLStreamConstants.START_ELEMENT ) {
                //System.out.println("readElement() - START ELEMENT FOUND!");
                String element = reader.getLocalName();
                //System.out.println("element = "+element);
                
                Class type = field.getType();
                Object item = type.newInstance();
                readAttributes(reader, type, item);
                
                //System.out.println("item = "+item);
                
                System.exit(0);
            }
        } catch(XMLStreamException e) {
            if( reader.getEventType() == XMLStreamConstants.CHARACTERS ) {
                String value = reader.getText().trim();
                field.set(obj, castItemValue(field.getType(), value));
            } else {
                throw e;
            }
        }
    }
    
    private Object castItemValue(Class type, String value) {
        if( type == String.class ) {
            return String.valueOf(value);
        }
        if( type == Boolean.class || type.getName().equals("boolean")) {
            return Boolean.valueOf(value);
        }
        if( type == Integer.class || type.getName().equals("int") ) {
            return Integer.valueOf(value);
        }
        if( type == Float.class || type.getName().equals("float")) {
            return Float.valueOf(value);
        }
        if( type == Double.class || type.getName().equals("double")) {
            return Double.valueOf(value);
        }
        if( type == Character.class || type.getName().equals("char")) {
            return value.charAt(0);
        }
        if( type == Byte.class || type.getName().equals("byte")) {
            return Byte.valueOf(value);
        }
        if( type == Short.class || type.getName().equals("short")) {
            return Short.valueOf(value);
        }
        if( type == Long.class || type.getName().equals("long")) {
            return Long.valueOf(value);
        }
        return type.cast(value);
    }
    
}
